/*  
 *  BoardEvaluator.cs - basic chess board evaluation class.
 * 
 *  Copyright(c) 2007 Adam W Adair
 *
 *  This file is part of Adam's Chess Engine (ACE).
 *
 *  ACE is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *   
 *  ACE is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with ACE; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
using System;
using System.Collections.Generic;
using System.Text;
using ACE.Board;

namespace ACE.Engine
{
    /// <summary>
    /// The BoarEvaluator can provide a numeric evaluation of a ChessBoard
    /// object for either side (white or black).
    /// </summary>
    class BoardEvaluator
    {
        ChessBoard m_board;
        const int LIGHT                     = 0;
        const int DARK                      = 1;
        const int EMPTY                     = -1;
        const int DOUBLED_PAWN_PENALTY		= 10;
        const int ISOLATED_PAWN_PENALTY		= 20;
        const int BACKWARDS_PAWN_PENALTY	= 8;
        const int PASSED_PAWN_BONUS			= 20;
        const int ROOK_SEMI_OPEN_FILE_BONUS	= 10;
        const int ROOK_OPEN_FILE_BONUS		= 15;
        const int ROOK_ON_SEVENTH_BONUS     = 20;


        private int[,] m_iaPawnRank;
        private int[] m_iaPieceMaterial;  // the value of a side's pieces 
        private int[] m_iaPawnMaterial;   // the value of a side's pawns 

        public BoardEvaluator(ChessBoard board)
        {
            m_board = board;
        }

        /// <summary>
        /// The flip array is used to calculate the piece/square
        /// values for Black pieces. The piece/square value of a
        /// LIGHT pawn is pawn_pcsq[sq] and the value of a DARK
        /// pawn is pawn_pcsq[flip[sq]] 
        /// </summary>
        private static readonly int[] FLIP = {
            56,  57,  58,  59,  60,  61,  62,  63,
            48,  49,  50,  51,  52,  53,  54,  55,
            40,  41,  42,  43,  44,  45,  46,  47,
            32,  33,  34,  35,  36,  37,  38,  39,
            24,  25,  26,  27,  28,  29,  30,  31,
            16,  17,  18,  19,  20,  21,  22,  23,
             8,   9,  10,  11,  12,  13,  14,  15,
             0,   1,   2,   3,   4,   5,   6,   7
        };

        /// <summary>
        /// Pawn piece square values in centi-pawns
        /// </summary>
        private static readonly int[] pawn_pcsq = {
	          0,   0,   0,   0,   0,   0,   0,   0,
	          5,  10,  15,  20,  20,  15,  10,   5,
	          4,   8,  12,  16,  16,  12,   8,   4,
	          3,   6,   9,  12,  12,   9,   6,   3,
	          2,   4,   6,   8,   8,   6,   4,   2,
	          1,   2,   3, -10, -10,   3,   2,   1,
	          0,   0,   0, -40, -40,   0,   0,   0,
	          0,   0,   0,   0,   0,   0,   0,   0
        };

        /// <summary>
        /// Knight piece square vlaues in centi-pawns
        /// </summary>
        private static readonly int[] knight_pcsq = {
	        -10, -10, -10, -10, -10, -10, -10, -10,
	        -10,   0,   0,   0,   0,   0,   0, -10,
	        -10,   0,   5,   5,   5,   5,   0, -10,
	        -10,   0,   5,  10,  10,   5,   0, -10,
	        -10,   0,   5,  10,  10,   5,   0, -10,
	        -10,   0,   5,   5,   5,   5,   0, -10,
	        -10,   0,   0,   0,   0,   0,   0, -10,
	        -10, -30, -10, -10, -10, -10, -30, -10
        };

        /// <summary>
        /// bishop piece sqaure values in centi-pawns
        /// </summary>
        private static readonly int[] bishop_pcsq = {
	        -10, -10, -10, -10, -10, -10, -10, -10,
	        -10,   0,   0,   0,   0,   0,   0, -10,
	        -10,   0,   5,   5,   5,   5,   0, -10,
	        -10,   0,   5,  10,  10,   5,   0, -10,
	        -10,   0,   5,  10,  10,   5,   0, -10,
	        -10,   0,   5,   5,   5,   5,   0, -10,
	        -10,   0,   0,   0,   0,   0,   0, -10,
	        -10, -10, -20, -10, -10, -20, -10, -10
        };

        /// <summary>
        /// king piece square values in centi-pawns
        /// </summary>
        private static readonly int[] king_pcsq = {
	        -40, -40, -40, -40, -40, -40, -40, -40,
	        -40, -40, -40, -40, -40, -40, -40, -40,
	        -40, -40, -40, -40, -40, -40, -40, -40,
	        -40, -40, -40, -40, -40, -40, -40, -40,
	        -40, -40, -40, -40, -40, -40, -40, -40,
	        -40, -40, -40, -40, -40, -40, -40, -40,
	        -20, -20, -20, -20, -20, -20, -20, -20,
	          0,  20,  40, -20,   0, -20,  40,  20
        };

        /// <summary>
        /// emd game king piece square values in centi-pawns
        /// </summary>
        private static readonly int[] king_endgame_pcsq = {
	          0,  10,  20,  30,  30,  20,  10,   0,
	         10,  20,  30,  40,  40,  30,  20,  10,
	         20,  30,  40,  50,  50,  40,  30,  20,
	         30,  40,  50,  60,  60,  50,  40,  30,
	         30,  40,  50,  60,  60,  50,  40,  30,
	         20,  30,  40,  50,  50,  40,  30,  20,
	         10,  20,  30,  40,  40,  30,  20,  10,
	          0,  10,  20,  30,  30,  20,  10,   0
        };

        /// <summary>
        /// returns the value of the piece that is sitting on a square.
        /// </summary>
        /// <param name="piece">Piece to evaluate</param>
        /// <param name="square">Square the piece is sitting on</param>
        /// <returns>value in centipawns</returns>
        private int PieceSquareValue(Piece piece, int square, bool isEndgame)
        {
            int indx = (piece.Color == Piece.WHITE)?square:FLIP[square];
            if (piece is Pawn)
            {
                if (piece.Color == Piece.WHITE)
                    return AdjustLightPawnScore(pawn_pcsq[indx], square);

                else
                    return AdjustDarkPawnScore(pawn_pcsq[indx], square);
            }
            if (piece is Knight)
                return knight_pcsq[indx];
            if (piece is Bishop)
                return bishop_pcsq[indx];
            if (piece is King)
            {
                if (isEndgame)
                {
                    return king_endgame_pcsq[indx];
                }
                else
                {
                    if (piece.Color == Piece.WHITE)
                        return AdjustLightKingScore(king_pcsq[indx], square);
                    else
                        return AdjustDarkKingScore(king_pcsq[indx], square);
                }
            }
            if (piece is Rook)
            {
                if (piece.Color == Piece.WHITE)
                    return EvalLightRookPosition(square);
                else
                    return EvalDarkRookPosition(square);
            }
            return 0;  // if it is queen or rook we don't care.      
        }

        /// <summary>
        /// Evaluates and scores the board for a given side.
        /// </summary>
        /// <param name="side">0 = white, 1 = black</param>
        /// <returns>The score.</returns>
        public int Evaluate(int side)
        {
            if (m_board == null) return 0;
            m_iaPawnRank = new int[2, 10];
            m_iaPieceMaterial = new int[2];  
            m_iaPawnMaterial = new int[2];   
            int i;
            int row;
            int f;  
            int color;
            int[] score = new int[2];  

            // first pass: set up pawn_rank, piece_mat, and pawn_mat. */
            for (i = 0; i < 10; ++i)
            {
                m_iaPawnRank[LIGHT,i] = 0;
                m_iaPawnRank[DARK,i] = 7;
            }

            m_iaPieceMaterial[LIGHT] = 0;
            m_iaPieceMaterial[DARK] = 0;
            m_iaPawnMaterial[LIGHT] = 0;
            m_iaPawnMaterial[DARK] = 0;
            BoardSquare[] sq = m_board.Squares;

            for (i = 0; i < 64; ++i)
            {
                if (sq[i].Owner == EMPTY)
                    continue;
                if (sq[i].Piece is Pawn)
                {
                    color = sq[i].Piece.ColorValue;
                    m_iaPawnMaterial[color] += sq[i].Piece.PieceValue;
                    f = ChessBoard.Column(i) + 1;  /* add 1 because of the extra file in the array */
                    row = ChessBoard.Row(i);
                    if (color == LIGHT)
                    {
                        if (m_iaPawnRank[LIGHT,f] < row)
                            m_iaPawnRank[LIGHT,f] = row;
                    }
                    else
                    {
                        if (m_iaPawnRank[DARK,f] > row)
                            m_iaPawnRank[DARK,f] = row;
                    }
                }
                else
                {
                    color = (int)sq[i].Piece.Color;
                    m_iaPieceMaterial[color] += sq[i].Piece.PieceValue;
                }
            }

            /* this is the second pass: evaluate each piece */
            score[LIGHT] = m_iaPieceMaterial[LIGHT] + m_iaPawnMaterial[LIGHT];
            score[DARK] = m_iaPieceMaterial[DARK] + m_iaPawnMaterial[DARK];
            bool isLightEndGame = (m_iaPieceMaterial[DARK] <= 1200);
            bool isDarkEndGame = (m_iaPieceMaterial[LIGHT] <= 1200);
            for (i = 0; i < 64; ++i)
            {
                if (sq[i].Owner == EMPTY)
                    continue;
                if (sq[i].Owner == LIGHT)
                {
                    score[LIGHT] += PieceSquareValue(sq[i].Piece, i, isLightEndGame);   
                }
                else
                {
                    score[DARK] += PieceSquareValue(sq[i].Piece, i, isDarkEndGame);
                }
            }
            

            // the score[] array is set, now return the score relative to the side 
            if (side == LIGHT)
                return score[LIGHT] - score[DARK];
            return score[DARK] - score[LIGHT];
        }

        /// <summary>
        /// Evaluates board relative to the side that is to move on the board.
        /// </summary>
        /// <returns></returns>
        public int Evaluate()
        {
            if (m_board == null) return 0;
            return Evaluate(m_board.SideToMove);
        }

        private int EvaluatePiece(Piece piece, int square, bool isEndGame)
        {
            if (piece is Knight || piece is Bishop) return PieceSquareValue(piece, square, isEndGame);
            return 0;
        }

        /// <summary>
        /// Adjust the raw pawn score based on various board factors.  This is 
        /// an adjustment from the light point of view.
        /// </summary>
        /// <param name="score">Raw score</param>
        /// <param name="sq">square pawn is sitting on</param>
        /// <returns>the adjusted score</returns>
        private int AdjustLightPawnScore(int score, int sq)
        {
            int r;  // the value to return 
            int f;  // the pawn's file 
            int row = ChessBoard.Row(sq);

            r = 0;
            f = ChessBoard.Column(sq) + 1;

            r += score;

            // if there's a pawn behind this one, it's doubled 
            if (m_iaPawnRank[LIGHT, f] > row)
                r -= DOUBLED_PAWN_PENALTY;

            // if there aren't any friendly pawns on either side of
            // this one, it's isolated 
            if ((m_iaPawnRank[LIGHT, f - 1] == 0) &&
                    (m_iaPawnRank[LIGHT, f + 1] == 0))
                r -= ISOLATED_PAWN_PENALTY;

            // if it's not isolated, it might be backwards 
            else if ((m_iaPawnRank[LIGHT, f - 1] < row) &&
                    (m_iaPawnRank[LIGHT, f + 1] < row))
                r -= BACKWARDS_PAWN_PENALTY;

            // add a bonus if the pawn is passed 
            if ((m_iaPawnRank[DARK, f - 1] >= row) &&
                    (m_iaPawnRank[DARK, f] >= row) &&
                    (m_iaPawnRank[DARK, f + 1] >= row))
                r += (7 - row) * PASSED_PAWN_BONUS;

            return r;
        }

        /// <summary>
        /// Adjust the raw pawn score based on various board factors.  This 
        /// is an adjustment from the black point of view.
        /// </summary>
        /// <param name="score">Raw score</param>
        /// <param name="sq">square pawn is sitting on</param>
        /// <returns>the adjusted score</returns>
        private int AdjustDarkPawnScore(int score, int sq)
        {
            int r;  // the value to return 
            int f;  // the pawn's file 
            int row = ChessBoard.Row(sq);
            
            r = 0;
            f = ChessBoard.Column(sq) + 1;
            r += score;

            // if there's a pawn behind this one, it's doubled 
            if (m_iaPawnRank[DARK, f] < row)
                r -= DOUBLED_PAWN_PENALTY;

            // if there aren't any friendly pawns on either side of
            // this one, it's isolated 
            if ((m_iaPawnRank[DARK, f - 1] == 7) &&
                    (m_iaPawnRank[DARK, f + 1] == 7))
                r -= ISOLATED_PAWN_PENALTY;

            // if it's not isolated, it might be backwards 
            else if ((m_iaPawnRank[DARK, f - 1] > row) &&
                    (m_iaPawnRank[DARK, f + 1] > row))
                r -= BACKWARDS_PAWN_PENALTY;

            // add a bonus if the pawn is passed 
            if ((m_iaPawnRank[LIGHT, f - 1] <= row) &&
                    (m_iaPawnRank[LIGHT, f] <= row) &&
                    (m_iaPawnRank[LIGHT, f + 1] <= row))
                r += row * PASSED_PAWN_BONUS;

            return r;
        }

        private int AdjustLightKingScore(int score, int sq)
        {
            int r;  // the value to return 
            int i;
            int col = ChessBoard.Column(sq);

            r = score;

            // if the king is castled, use a special function to evaluate the
            //   pawns on the appropriate side 
            if (col < 3)
            {
                r += EvalLightKingFPawn(1);
                r += EvalLightKingFPawn(2);
                r += EvalLightKingFPawn(3) / 2;  // problems with pawns on the c & f files
								                 // are not as severe 
            }
            else if (col > 4)
            {
                r += EvalLightKingFPawn(8);
                r += EvalLightKingFPawn(7);
                r += EvalLightKingFPawn(6) / 2;
            }

            // otherwise, just assess a penalty if there are open files near
            //   the king 
            else
            {
                for (i = col; i <= col + 2; ++i)
                    if ((m_iaPawnRank[LIGHT, i] == 0) &&
                            (m_iaPawnRank[DARK, i] == 7))
                        r -= 10;
            }

            // scale the king safety value according to the opponent's material;
            // the premise is that your king safety can only be bad if the
            // opponent has enough pieces to attack you 
            r *= m_iaPieceMaterial[DARK];
            r /= 3100;

            return r;
        }

        // Evaluates the Light King Pawn on file f 
        private int EvalLightKingFPawn(int f)
        {
            int r = 0;

            if (m_iaPawnRank[LIGHT, f] == 6) { }  // pawn hasn't moved */
            else if (m_iaPawnRank[LIGHT, f] == 5)
                r -= 10;  // pawn moved one square 
            else if (m_iaPawnRank[LIGHT, f] != 0)
                r -= 20;  // pawn moved more than one square 
            else
                r -= 25;  // no pawn on this file 

            if (m_iaPawnRank[DARK, f] == 7)
                r -= 15;  // no enemy pawn 
            else if (m_iaPawnRank[DARK, f] == 5)
                r -= 10;  // enemy pawn on the 3rd rank 
            else if (m_iaPawnRank[DARK, f] == 4)
                r -= 5;   // enemy pawn on the 4th rank 

            return r;
        }

        private int AdjustDarkKingScore(int score, int sq)
        {
            int r;
            int i;
            int col = ChessBoard.Column(sq);

            r = score;
            if (col < 3)
            {
                r += EvalDarkKingFPawn(1);
                r += EvalDarkKingFPawn(2);
                r += EvalDarkKingFPawn(3) / 2;
            }
            else if (col > 4)
            {
                r += EvalDarkKingFPawn(8);
                r += EvalDarkKingFPawn(7);
                r += EvalDarkKingFPawn(6) / 2;
            }
            else
            {
                for (i = col; i <= col + 2; ++i)
                    if ((m_iaPawnRank[LIGHT, i] == 0) &&
                            (m_iaPawnRank[DARK, i] == 7))
                        r -= 10;
            }
            r *= m_iaPieceMaterial[LIGHT];
            r /= 3100;
            return r;
        }

        private int EvalDarkKingFPawn(int f)
        {
            int r = 0;

            if (m_iaPawnRank[DARK, f] == 1) { }
            else if (m_iaPawnRank[DARK, f] == 2)
                r -= 10;
            else if (m_iaPawnRank[DARK, f] != 7)
                r -= 20;
            else
                r -= 25;

            if (m_iaPawnRank[LIGHT, f] == 0)
                r -= 15;
            else if (m_iaPawnRank[LIGHT, f] == 2)
                r -= 10;
            else if (m_iaPawnRank[LIGHT, f] == 3)
                r -= 5;

            return r;
        }

        private int EvalLightRookPosition(int sq)
        {
            int col = ChessBoard.Column(sq);
            int row = ChessBoard.Row(sq);
            int score = 0;
            if (m_iaPawnRank[LIGHT , col + 1] == 0)
            {
                if (m_iaPawnRank[DARK , col + 1] == 7)
                    score += ROOK_OPEN_FILE_BONUS;
                else
                    score += ROOK_SEMI_OPEN_FILE_BONUS;
            }
            if (row == 1)
                score += ROOK_ON_SEVENTH_BONUS;
            return score;
        }

        private int EvalDarkRookPosition(int sq)
        {
            int col = ChessBoard.Column(sq);
            int row = ChessBoard.Row(sq);
            int score = 0;
            if (m_iaPawnRank[DARK, col + 1] == 7)
            {
                if (m_iaPawnRank[LIGHT , col + 1] == 0)
                    score += ROOK_OPEN_FILE_BONUS;
                else
                    score += ROOK_SEMI_OPEN_FILE_BONUS;
            }
            if (row == 6)
                score += ROOK_ON_SEVENTH_BONUS;
            return score;
        }

    }
}